package com.lazy.utils.datasource;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

@Configuration
@ConditionalOnBean(name = "masterDataSource")
@AutoConfigureAfter(DataSourceConfiguration.class)
@Slf4j
public class MybatisConfiguration {

    /**
     * XxxMapper.xml文件所在路径
     */
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    /**
     * 加载全局的配置文件
     */
    @Value("${mybatis.configLocation}")
    private String configLocation;

    @Value("${mybatis.typeAliasesPackage}")
    private String typeAliasesPackage;

    @Autowired
    @Qualifier("masterDataSource")
    private DataSource masterDataSource;

    @Autowired(required = false)
    @Qualifier("slaveDataSource1")
    private DataSource slaveDataSource1;

    @Autowired(required = false)
    @Qualifier("slaveDataSource2")
    private DataSource slaveDataSource2;

    @Autowired(required = false)
    @Qualifier("slaveDataSource3")
    private DataSource slaveDataSource3;

    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() {
        log.info("------------------  sqlSessionFactory init ---------------------");
        try {
            SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            sessionFactoryBean.setDataSource(roundRobinDataSourceProxy());
            //读取配置
            sessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
            //设置mapper.xml文件所在位置 
            Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations);
            sessionFactoryBean.setMapperLocations(resources);
            //设置mybatis-config.xml配置文件位置
            sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
            return sessionFactoryBean.getObject();
        } catch (IOException e) {
            log.error("mybatis resolver mapper*xml is error",e);
            return null;
        } catch (Exception e) {
            log.error("mybatis sqlSessionFactoryBean create error",e);
            return null;
        }
    }

    /**
     * 把所有数据库都放在路由中
     * @return
     */
    @Bean(name="roundRobinDataSourceProxy")
    public AbstractRoutingDataSource roundRobinDataSourceProxy() {
        List<DataSource> slaveDataSourceList = new ArrayList<>();
        slaveDataSourceList.add(slaveDataSource1);
        slaveDataSourceList.add(slaveDataSource2);
        slaveDataSourceList.add(slaveDataSource3);
        Map<Object, Object> targetDataSources = new HashMap<>(4);
        targetDataSources.put(DataSourceTypeEnum.master.getType(), masterDataSource);

        //把所有数据库都放在targetDataSources中,注意key值要和determineCurrentLookupKey()中代码写的一至，
        //否则切换数据源时找不到正确的数据源
        for (int i = 0; i < slaveDataSourceList.size() ; i++) {
            if (slaveDataSourceList.get(i) != null) {
                targetDataSources.put(DataSourceTypeEnum.slave.getType() + (i + 1), slaveDataSourceList.get(i));
            }
        }
        //路由类，寻找对应的数据源
        AbstractRoutingDataSource proxy = new AbstractRoutingDataSource() {
            private AtomicInteger count = new AtomicInteger(0);
            /**
             * 这是AbstractRoutingDataSource类中的一个抽象方法，
             * 而它的返回值是你所要用的数据源dataSource的key值，有了这个key值，
             * targetDataSources就从中取出对应的DataSource，如果找不到，就用配置默认的数据源。
             */
        	@Override
        	protected Object determineCurrentLookupKey() {
        	    try {
                    String typeKey = DataSourceContextHolder.getMasterOrSlave();

                    if (typeKey == null) {
                        log.info("当前事务数据源为 master typeKey:null");
                        return DataSourceTypeEnum.master.getType();
                    }

                    if (targetDataSources.size() == 1) {
                        log.info("当前事务数据源为 master typeKey:{}", typeKey);
                        return DataSourceTypeEnum.master.getType();
                    }

                    if (DataSourceTypeEnum.master.getType().equals(typeKey)) {
                        log.info("当前事务数据源为 master typeKey:{}", typeKey);
                        return DataSourceTypeEnum.master.getType();
                    }

                    if (slaveDataSourceList.size() <= 0) {
                        log.info("当前事务数据源为 master typeKey:{}", typeKey);
                        return DataSourceTypeEnum.master.getType();
                    }

                    //读库，简单负载均衡
                    if (count.get() < 0 || count.get() >= Integer.MAX_VALUE) {
                        count.set(1);
                    }
                    int number = count.getAndAdd(1);
                    int lookupKey = number % (targetDataSources.size() - 1) + 1;
                    log.info("当前事务数据源为 slave0" + lookupKey);
                    return DataSourceTypeEnum.slave.getType() + lookupKey;
                } finally {
                    DataSourceContextHolder.clear();
                }
        	}
        };
        //默认库
        proxy.setDefaultTargetDataSource(masterDataSource);
        proxy.setTargetDataSources(targetDataSources);
        return proxy;
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    	return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManagers() {
        log.info("-------------------- transactionManager init ---------------------");
        return new DataSourceTransactionManager(roundRobinDataSourceProxy());
    }
}
